package net.javlov.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

import org.jdom.Element;

public class ObjectFactory {
	
	public static Object createObject( Element e) {
		
		String name = e.getAttributeValue("name");
		
		e = e.getChild("arguments");
		
		String[] argTypes;
		Object[] args;
		
		if ( e != null ) {
			List<Element> argList = e.getChildren(); //either primitive args or other objects
			
			argTypes = new String[argList.size()];
			args = new Object[argList.size()];
			
			int i = 0;
			for (Element arg : argList) {
				if ( arg.getName().equals("javaclass") ) {
					argTypes[i] = arg.getAttributeValue("name");
					args[i++] = createObject(arg);
				} else { //element is primitive arg
					argTypes[i] = arg.getAttributeValue("type");
					args[i++] = arg.getValue();
				}
			}
		}
		else {
			argTypes = new String[0];
			args = new Object[0];
		}
		
		return create(name, argTypes, args);
	}

	public static Object create(String name, String[] argTypes, Object[] args) {
		
		Class<?> c = getClassFromName(name);
		Class<?>[] argClasses = parseTypes(argTypes);
		Constructor<?> constr = null;
		
		try {
			constr = c.getDeclaredConstructor(argClasses);
		} catch(NoSuchMethodException e) {
			constr = findConstructorForSupertypeArgs(c, argClasses);
			if ( constr == null ) {
				System.out.println("WARNING: No constructors with supertype arguments found.");
				return null;
			}
		}
		
		Object[] argArray = createArgArray(argTypes, args);
		Object o;
		try {
			o = constr.newInstance(argArray);
		} catch (IllegalArgumentException e) {
			System.err.println("IllegalArgumentException " + name + ": " + e);
			return null;
		} catch (InvocationTargetException e) {
			System.err.println("InvocationTargetException " + name + ": " + e);
			return null;
		} catch (ExceptionInInitializerError e) {
			System.err.println("ExceptionInInitializerError " + name + ": " + e);
			return null;
		} catch (Exception e) {
			System.err.println("Other except " + name + ": " + e);
			return null;
		}
		
		return o;
	}

	private static Constructor<?> findConstructorForSupertypeArgs( Class<?> c, Class<?>[] args ) {
		Constructor<?>[] constructors = c.getDeclaredConstructors();
		for (int i = 0; i < constructors.length; i++) {
            Class<?> paramTypes[] = constructors[i].getParameterTypes();
            if ( paramTypes.length == args.length ) {
            	int j;
            	for ( j = 0; j < args.length; j++ )
            		if ( !paramTypes[j].isAssignableFrom(args[j]) )
            			break;
            	if ( j == args.length ) //all argument types match
            		return constructors[i];
            }
		}
		return null;
	}
	
	private static Class<?>[] parseTypes(String[] argTypes) {
		Class<?>[] c = new Class<?>[argTypes.length];
		
		for ( int i = 0; i < c.length; i++ ) {
			c[i] = classOf(argTypes[i]);
		}
		return c;
	}
	
	private static Class<?> classOf( String type ) {
		if (type.equals("byte"))
			return byte.class;
		else if (type.equals("short"))
			return short.class;
		else if (type.equals("int"))
			return int.class;
		else if (type.equals("long"))
			return long.class;
		else if (type.equals("float"))
			return float.class;
		else if (type.equals("double"))
			return double.class;
		else if (type.equals("boolean"))
			return boolean.class;
		else if (type.equals("char"))
			return char.class;
		else							//not a primitive type
			return getClassFromName(type);	
	}
	
	private static Class<?> getClassFromName(String name) {
		Class<?> c;
		try {
			c = Class.forName(name);
		} catch (ClassNotFoundException e) {
			System.err.println("Could not find class " + name + ": " + e.getMessage());
			return null;
		}
		return c;
	}
	
	private static Object[] createArgArray( String[] argTypes, Object[] args ) {
		Object[] o = new Object[args.length];
		for ( int i = 0; i < o.length; i++ ) {
			o[i] = createInstanceOf( argTypes[i], args[i] );
		}
		return o;
	}
	
	private static Object createInstanceOf( String type, Object val ) {
		//no primitive type, so it's already the object we need
		if ( !(val instanceof String) || type.equals("String") )
			return val;
		
		String strVal = (String) val;
		if (type.equals("byte"))
			return Byte.valueOf(strVal);
		else if (type.equals("short"))
			return Short.valueOf(strVal);
		else if (type.equals("int"))
			return Integer.valueOf(strVal);
		else if (type.equals("long"))
			return Long.valueOf(strVal);
		else if (type.equals("float"))
			return Float.valueOf(strVal);
		else if (type.equals("double"))
			return Double.valueOf(strVal);
		else if (type.equals("boolean"))
			return Boolean.valueOf(strVal);
		else if (type.equals("char"))
			return Character.valueOf(strVal.charAt(0));

		System.out.println("createInstanceOf: should never get here!");
		return null;
	}
}